from threading import Thread, Lock
import copy
import math


class Progress:

    """Describes the progress of some action."""

    def __init__(self, current, total):
        total = max(total, 0)
        current = max(current, 0)
        current = min(current, total)
        self.current = current
        self.total = total

    def get_current(self):
        return self.current

    def get_total(self):
        return self.total

    def get_fraction(self):
        return float(self.current) / float(self.total)

    def text_percent(self, precision=0):
        percent = math.floor(self.get_fraction() * 100)
        return (("%." + str(precision) + "f") %  percent) + "%"

    def text_fraction(self):
        return "%d/%d" % (self.current, self.total)


class Worker(Thread):
    
    """Abstract base class for background workers with progress updates.
    
    Derived classes should not inherit directly from Worker, but instead
    from WorkerSingle or WorkerParallel.

    """

    def __init__(self, block):
        """Constructs a Worker.
        
        Parameters:
        block -- Chunk size to process before updating progress.

        """
        Thread.__init__(self, target=self.supervise)
        self.block = block
        self.progressLock = Lock()
        self.result = None
        self.error = None

    def supervise(self):
        """Runs the to-be-implemented work() function, catching any Exception.
        
        Target function for Thread.run().
        Calls work(), which must be implemented by a subclass.
        Catches any Exception raised, making it available at Worker.error

        """
        try:
            self.work()
        except Exception, ex:
            self.error = ex


class WorkerSingle(Worker):
    
    """Abstract base class for background processing."""
    
    def __init__(self, block):
        """Constructs a DownloaderSingle.
        
        Parameters:
        block -- Chunk size to process before updating progress.
        
        """
        Worker.__init__(self, block)
        self.progress = None

    def set_progress(self, current, total):
        """Sets the current progress. Internal use only."""
        self.progressLock.acquire()
        self.progress = Progress(current, total)
        self.progressLock.release()

    def get_progress(self):
        """Gets the current progress of tile (x, y).

        Returns:
        A tuple (current, total), or None if worker hasn't started yet.

        """
        self.progressLock.acquire()
        progress = copy.copy(self.progress)
        self.progressLock.release()
        return progress


class WorkerParallel(Worker):
    
    """Abstract base class for parallel background processing.
    
    Subclasses must declare and initialise an indexed variable self.progress.

    """
    
    def __init__(self, block, parallel):
        """Constructs a WorkerParallel.
        
        Parameters:
        block -- Chunk size to process before updating progress.
        parallel -- Maximum number of concurrent tasks.
        
        """
        Worker.__init__(self, block)
        self.parallel = parallel

    def set_progress(self, index, current, total):
        self.progressLock.acquire()
        self.progress[index] = Progress(current, total)
        self.progressLock.release()

    def get_progress(self, index):
        self.progressLock.acquire()
        progress = copy.copy(self.progress[index])
        self.progressLock.release()
        return progress


